package com.test.myestest.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.myestest.service.ESService;
import com.test.myestest.util.ESUtil;
import com.test.myestest.vo.DocumentTest;
import com.test.myestest.vo.ESSearchFileVo;
import com.test.myestest.vo.ESSearchVo;
import com.test.myestest.vo.FileTest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.*;

@Service
public class ESServiceImpl implements ESService {

    private static final Logger logger = LoggerFactory.getLogger(ESServiceImpl.class);

    @Resource
    private ElasticsearchClient elasticsearchClient;

    ObjectMapper objectMapper = new ObjectMapper();

    private final static Set<String> allowedFormats = new HashSet<>(Arrays.asList("doc", "docx", "pdf", "xls", "xlsx", "txt"));


    /**
     * es-检索
     *
     * @param esSearchVo 检索条件
     * @return result
     */
    @Override
    public Map<String, Object> search(ESSearchVo esSearchVo) {
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        //根据业务需求拼接查询条件
        if (StringUtils.isNotBlank(esSearchVo.getDocumentType())) {
            boolQueryBuilder.filter(
                    TermQuery.of(tq -> tq.field("documentType").value(esSearchVo.getDocumentType()))._toQuery()
            );
        }

        if (esSearchVo.getStartDate() != null) {
            boolQueryBuilder.filter(
                    RangeQuery.of(rq -> rq.field("createDate").gte(JsonData.of(esSearchVo.getStartDate())))._toQuery()
            );
        }

        if (esSearchVo.getEndDate() != null) {
            boolQueryBuilder.filter(
                    RangeQuery.of(rq -> rq.field("createDate").lte(JsonData.of(esSearchVo.getEndDate())))._toQuery()
            );
        }

        //检索主要字段必需
        if (StringUtils.isNotBlank(esSearchVo.getSearchKey()) && StringUtils.isNotEmpty(esSearchVo.getSearchValue())) {
            boolQueryBuilder.must(
                    MatchQuery.of(mq -> mq.field(esSearchVo.getSearchKey()).query(esSearchVo.getSearchValue()).operator(Operator.And))._toQuery()
            );
        }

        //创建索引时定义了该字段的","分词器，用来模拟权限功能
        if (StringUtils.isNotBlank(esSearchVo.getUserIds())) {
            boolQueryBuilder.must(
                    MatchQuery.of(mq -> mq.field("userIds").query(esSearchVo.getUserIds()).operator(Operator.And))._toQuery()
            );
        }

        // 构建最终的 Query 对象
        Query boolQuery = boolQueryBuilder.build()._toQuery();

        //排序 不传排序相关字段的值默认根据匹配结果的相关性排序，传值则根据所传字段和排序规则字段进行排序
        SortOptions.Builder sortOptions = new SortOptions.Builder();
        if (StringUtils.isNotBlank(esSearchVo.getSortKey())) {
            if ("asc".equals(esSearchVo.getSortOrder())) {
                sortOptions.field(FieldSort.of(fs -> fs.field(esSearchVo.getSortKey()).order(SortOrder.Asc)));
            } else if ("desc".equals(esSearchVo.getSortOrder())) {
                sortOptions.field(FieldSort.of(fs -> fs.field(esSearchVo.getSortKey()).order(SortOrder.Desc)));
            } else {
                sortOptions.field(FieldSort.of(fs -> fs.field(esSearchVo.getSortKey()).order(SortOrder.Desc)));
            }
        }

        // 配置高亮
        Highlight.Builder highlightBuilder = new Highlight.Builder();
        HighlightField.Builder highlightFieldBuilder = new HighlightField.Builder();
        highlightFieldBuilder.preTags("<font color='red'>");
        highlightFieldBuilder.postTags("</font>");
        highlightFieldBuilder.fragmentSize(150);
        //highlightFieldBuilder.noMatchSize(150);
        highlightFieldBuilder.numberOfFragments(1);
        highlightBuilder.fields(esSearchVo.getSearchKey(), highlightFieldBuilder.build());


        //聚合搜索 根据业务需求是否增加此功能 非必需
        Map<String, Aggregation> aggregationDefinition = null;
        if (StringUtils.isNotBlank(esSearchVo.getAggregateKey())) {
            // 创建一个简单的术语聚合（Term Aggregation）作为示例
            Aggregation aggregation = Aggregation.of(a -> a.terms(TermsAggregation.of(s -> s.field(esSearchVo.getAggregateKey()).size(esSearchVo.getAggregateNum() != null ? esSearchVo.getAggregateNum() : 10))));
            aggregationDefinition = new HashMap<>();
            aggregationDefinition.put(esSearchVo.getAggregateKey(), aggregation);
        }

        // 构建搜索请求
        SearchRequest.Builder searchRequest = new SearchRequest.Builder();
        searchRequest.index(esSearchVo.getIndexName());
        searchRequest.query(boolQuery);
        searchRequest.highlight(highlightBuilder.build());
        searchRequest.from(esSearchVo.getPageNum());
        searchRequest.size(esSearchVo.getPageSize());

        //根据需求增加的检索条件 排序和聚合 非必需
        if (StringUtils.isNotBlank(esSearchVo.getSortKey())) {
            searchRequest.sort(sortOptions.build());
        }
        if (aggregationDefinition != null) {
            searchRequest.aggregations(aggregationDefinition);
        }

        //执行查询，处理返回结果
        Map<String, Object> map = new HashMap<>();
        map.put("pageNum", esSearchVo.getPageNum());
        map.put("pageSize", esSearchVo.getPageSize());
        try {
            SearchResponse<Object> search = elasticsearchClient.search(searchRequest.build(), Object.class);
            List<Hit<Object>> hits = search.hits().hits();
            List<Map<String, Object>> list = new ArrayList<>();
            for (Hit<Object> hit : hits) {
                Object source = hit.source();
                Map<String, Object> m = objectMapper.convertValue(source, Map.class);
                Map<String, List<String>> highlights = hit.highlight();
                if (highlights != null && highlights.containsKey(esSearchVo.getSearchKey())) {
                    List<String> highLightValues = highlights.get(esSearchVo.getSearchKey());
                    if (!highLightValues.isEmpty()) {
                        m.put("highlight", highLightValues.get(0));
                    } else {
                        m.put("highlight", "");
                    }
                } else {
                    m.put("highlight", "");
                }
                list.add(m);
            }

            //获取聚合结果
            if (search.aggregations() != null && search.aggregations().get(esSearchVo.getAggregateKey()) != null) {
                Aggregate aggregate = search.aggregations().get(esSearchVo.getAggregateKey());
                StringTermsAggregate sterms = aggregate.sterms();
                List<Map<String, Object>> termsList = new ArrayList<>();
                for (StringTermsBucket bucket : sterms.buckets().array()) {
                    Map<String, Object> termsMap = new HashMap<>();
                    termsMap.put(bucket.key().stringValue(), bucket.docCount());
                    termsList.add(termsMap);
                }
                map.put("aggreate", termsList);
            }

            map.put("data", list);
            map.put("total", hits.size());
            int a, b, totalPage;
            a = hits.size() / esSearchVo.getPageSize();
            b = hits.size() % esSearchVo.getPageSize();
            totalPage = b != 0 ? a + 1 : a;
            map.put("totalPage",totalPage);
            map.put("msg", "success");
            return map;
        } catch (Exception e) {
            logger.error("查询异常：" + e.getMessage() + "esSearchVo:" + esSearchVo);
            e.printStackTrace();
            map.put("data", "");
            map.put("total", 0);
            map.put("totalPage", 0);
            map.put("msg", e.getMessage());
            return map;
        }
    }

    /**
     * es-添加数据，修改数据（id已存在为修改）
     *
     * @param indexName    索引名
     * @param documentTest 索引对象
     * @return result
     */
    @Override
    public String add(String indexName, DocumentTest documentTest) {
        IndexRequest<DocumentTest> request = new IndexRequest.Builder<DocumentTest>()
                .index(indexName)
                .id(documentTest.getObjectId()) // 自动生成 ID，如果需要指定 ID，可以设置为具体值
                .document(documentTest)
                .build();

        try {
            IndexResponse response = elasticsearchClient.index(request);
            logger.info("添加数据完成" + response.result().jsonValue());
            return response.result().jsonValue();
        } catch (Exception e) {
            logger.error("添加数据异常：" + e.getMessage() + "数据为" + documentTest);
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * es-删除数据
     *
     * @param indexName 索引名
     * @param id        数据主键id
     * @return result
     */
    @Override
    public String delete(String indexName, String id) {
        if (StringUtils.isBlank(indexName)) {
            return "索引名不能为空";
        }
        if (StringUtils.isBlank(id)) {
            return "id不能为空";
        }
        DeleteRequest request = new DeleteRequest.Builder()
                .index(indexName)
                .id(id)
                .build();

        try {
            DeleteResponse delete = elasticsearchClient.delete(request);
            logger.info("删除完成" + delete.result().jsonValue() + "索引名:" + indexName + "id:" + id);
            return delete.result().jsonValue();
        } catch (Exception e) {
            logger.error("删除异常" + e.getMessage() + "indexName:" + indexName + "--id:" + id);
            e.printStackTrace();
            return "删除失败" + e.getMessage();
        }
    }

    /**
     * es-添加文件，修改文件（id已存在为修改）
     *
     * @param multipartFile 文件multipartFile 2选1
     * @param importFileUrl 文件下载地址       2选1
     * @param indexName     索引名
     * @param fileTest      文件索引对象
     * @return result
     */
    @Override
    public String addFile(MultipartFile multipartFile, String importFileUrl, String indexName, FileTest fileTest) {
        try {
            //验证文件格式
            if(!isValidFormat(fileTest.getFileName().substring(fileTest.getFileName().lastIndexOf(".")+1))){
                return "文件格式不支持";
            }

            //获取文件方式：multipartFile方式上传或者importFileUrl文件地址方式上传
            String s, m;
            if (multipartFile != null) {
                s = ESUtil.multipartFileToBase64(multipartFile);
                m = multipartFile.getOriginalFilename();
            } else if (StringUtils.isNotBlank(importFileUrl)) {
                s = ESUtil.pdfToBase64(importFileUrl);
                m = importFileUrl;
            } else {
                return "获取文件失败,请选择一种提供文件的方式";
            }


            //如果文件获取大小为0,打印日志
            if (s.length() <= 0) {
                logger.error("获取文件失败");
                if (multipartFile != null) {
                    logger.error("multipartFile" + multipartFile.getOriginalFilename());
                } else {
                    logger.error("importFileUrl" + importFileUrl);
                }
            }


            //添加索引字段
            Map<String, Object> map = new HashMap<>();
            map.put("objectId", fileTest.getObjectId());
            map.put("fileName", fileTest.getFileName());
            map.put("data", s);//文件内容字段

            // 创建 IndexRequest
            IndexRequest<Map> request = IndexRequest.of(req -> req
                    .index("file-test")
                    .id(fileTest.getObjectId())
                    .pipeline("attachment") // 使用 attachment pipeline
                    .document(map));

            // 执行请求
            IndexResponse response = elasticsearchClient.index(request);
            logger.info("添加文件完成" + response.result().jsonValue() + "文件信息:" + m);
            return response.result().jsonValue();
        } catch (Exception e) {
            logger.error("添加文件异常" + e.getMessage());
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * es-文件检索
     *
     * @param esSearchFileVo 文件检索条件
     * @return result
     */
    @Override
    public Map<String, Object> searchFile(ESSearchFileVo esSearchFileVo) {
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        //文件内容检索
        if (StringUtils.isNotEmpty(esSearchFileVo.getSearchValue())) {
            boolQueryBuilder.must(
                    MatchQuery.of(mq -> mq.field("attachment.content").query(esSearchFileVo.getSearchValue()).operator(Operator.And))._toQuery()
            );
        }

        // 构建最终的 Query 对象
        Query boolQuery = boolQueryBuilder.build()._toQuery();

        // 配置高亮
        Highlight.Builder highlightBuilder = new Highlight.Builder();
        HighlightField.Builder highlightFieldBuilder = new HighlightField.Builder();
        highlightFieldBuilder.preTags("<font color='red'>");
        highlightFieldBuilder.postTags("</font>");
        highlightFieldBuilder.fragmentSize(150);
        //highlightFieldBuilder.noMatchSize(150);
        highlightFieldBuilder.numberOfFragments(1);
        highlightBuilder.fields("attachment.content", highlightFieldBuilder.build());

        //设置返回结果中不需要返回显示的字段，提高效率
        SourceFilter sourceFilter = SourceFilter.of(f -> f.excludes("attachment"));
        SourceConfig sourceConfig = SourceConfig.of(f -> f.filter(sourceFilter));

        //排序 不传排序相关字段的值默认根据匹配结果的相关性排序，传值则根据所传字段和排序规则字段进行排序
        SortOptions.Builder sortOptions = new SortOptions.Builder();
        if (StringUtils.isNotBlank(esSearchFileVo.getSortKey())) {
            if ("asc".equals(esSearchFileVo.getSortOrder())) {
                sortOptions.field(FieldSort.of(fs -> fs.field(esSearchFileVo.getSortKey()).order(SortOrder.Asc)));
            } else if ("desc".equals(esSearchFileVo.getSortOrder())) {
                sortOptions.field(FieldSort.of(fs -> fs.field(esSearchFileVo.getSortKey()).order(SortOrder.Desc)));
            } else {
                sortOptions.field(FieldSort.of(fs -> fs.field(esSearchFileVo.getSortKey()).order(SortOrder.Desc)));
            }
        }


        SearchRequest.Builder searchRequest = new SearchRequest.Builder();
        searchRequest.index(esSearchFileVo.getIndexName());
        searchRequest.query(boolQuery);
        searchRequest.highlight(highlightBuilder.build());
        searchRequest.from(esSearchFileVo.getPageNum());
        searchRequest.size(esSearchFileVo.getPageSize());
        searchRequest.source(sourceConfig);
        if (StringUtils.isNotBlank(esSearchFileVo.getSortKey())) {
            searchRequest.sort(sortOptions.build());
        }


        Map<String, Object> map = new HashMap<>();
        map.put("pageNum", esSearchFileVo.getPageNum());
        map.put("pageSize", esSearchFileVo.getPageSize());
        try {
            SearchResponse<Object> search = elasticsearchClient.search(searchRequest.build(), Object.class);
            List<Hit<Object>> hits = search.hits().hits();
            List<Map<String, Object>> list = new ArrayList<>();
            for (Hit<Object> hit : hits) {
                Object source = hit.source();
                Map<String, Object> m = objectMapper.convertValue(source, Map.class);
                Map<String, List<String>> highlights = hit.highlight();
                if (highlights != null && highlights.containsKey("attachment.content")) {
                    List<String> highLightValues = highlights.get("attachment.content");
                    if (!highLightValues.isEmpty()) {
                        m.put("highlight", highLightValues.get(0));
                    } else {
                        m.put("highlight", "");
                    }
                } else {
                    m.put("highlight", "");
                }
                list.add(m);
            }

            map.put("data", list);
            map.put("total", hits.size());
            int a, b, totalPage;
            a = hits.size() / esSearchFileVo.getPageSize();
            b = hits.size() % esSearchFileVo.getPageSize();
            totalPage = b != 0 ? a + 1 : a;
            map.put("totalPage",totalPage);
            map.put("msg", "success");
            return map;
        } catch (Exception e) {
            logger.error("查询文件异常：" + e.getMessage() + "esSearchFileVo:" + esSearchFileVo);
            e.printStackTrace();
            map.put("data", "");
            map.put("total", 0);
            map.put("totalPage",0);
            map.put("msg", e.getMessage());
            return map;
        }
    }

    // 判断文件格式
    public static boolean isValidFormat(String fileType) {
        if (fileType == null || fileType.isEmpty()) {
            return false;
        }
        return allowedFormats.contains(fileType.toLowerCase());
    }

}
